#include "procinfowidget.h"

#include <DFrame>
#include <DLabel>
#include <DApplicationHelper>

#include <QHBoxLayout>
#include <QDBusInterface>
#include <QMenu>
#include <QWidgetAction>

#include <unistd.h>
#include <stdio.h>

const double UpdateIntervalS = 1.0;

ProcInfoWidget::ProcInfoWidget(QWidget *parent)
    : QWidget(parent)
    , m_netMonitorInter(nullptr)
    , m_enableNetDataMonitor(false)
    , m_speedLabel(nullptr)
{
    registerProcInfoMetaType();
    registerProcInfoListMetaType();

    m_netMonitorInter = new QDBusInterface(CCC_SYS_DAEMON_DBUS_NAME,
                                           CCC_SYS_DAEMON_DBUS_PATH,
                                           CCC_SYS_DAEMON_DBUS_IFC,
                                           QDBusConnection::systemBus(), this);

    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QHBoxLayout *mainLayout = new QHBoxLayout;
    mainLayout->setContentsMargins(0, 0, 0, 0);
    setLayout(mainLayout);

    DFrame *bgFrame = new DFrame;
    bgFrame->setFrameRounded(false);
    bgFrame->setFrameShape(QFrame::Shape::NoFrame);
    mainLayout->addWidget(bgFrame);

    QHBoxLayout *bgLayout = new QHBoxLayout;
    bgLayout->setContentsMargins(0, 0, 0, 0);
    bgFrame->setLayout(bgLayout);

    m_speedLabel = new DLabel("0.0K/s");
    m_speedLabel->setAlignment(Qt::AlignmentFlag::AlignHCenter | Qt::AlignmentFlag::AlignVCenter);
    QPalette paTmp = m_speedLabel->palette();
    paTmp.setColor(QPalette::Text, Qt::GlobalColor::white);
    m_speedLabel->setPalette(paTmp);
    bgLayout->addWidget(m_speedLabel);

    // context menu
    QMenu *m_contextMenu = new QMenu(this);
    QWidgetAction *sysMonitorAction = new QWidgetAction(this);
    ActionWidget *sysMonitorActionWidget = new ActionWidget(QIcon::fromTheme("deepin-system-monitor"), "系统监视器", this);
    sysMonitorAction->setDefaultWidget(sysMonitorActionWidget);
    QWidgetAction *settingsAction = new QWidgetAction(this);
    ActionWidget *settingsActionWidget = new ActionWidget(QIcon(), "设置", this);
    settingsAction->setDefaultWidget(settingsActionWidget);
    QWidgetAction *aboutAction = new QWidgetAction(this);
    ActionWidget *aboutActionWidget = new ActionWidget(QIcon(), "关于", this);
    aboutAction->setDefaultWidget(aboutActionWidget);
    QList<QAction *> m_menuActionList;
    m_menuActionList << sysMonitorAction << settingsAction << aboutAction;
    m_contextMenu->addActions(m_menuActionList);
    m_contextMenu->hide();
    // setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
    // 右键菜单信号连接
    connect(this, &ProcInfoWidget::customContextMenuRequested, this, [m_contextMenu](const QPoint &) {
        qDebug() << "customContextMenuRequested";
        m_contextMenu->popup(QCursor::pos());;
    });
    connect(sysMonitorAction, &QAction::triggered, this, [](bool) {
        QProcess::startDetached("deepin-system-monitor", QStringList());;
    });
    connect(settingsAction, &QAction::triggered, this, [](bool) {
        QProcess::startDetached("/opt/apps/com.github.ccc-proc-info-plugin/files/tools/proc-info-window",
                                QStringList{QString("--%1").arg(EXEC_PARAMS_CFG_KEY_OPEN_SETTINGS_PAGE)});;
    });
    connect(aboutAction, &QAction::triggered, this, [](bool) {
        QProcess::startDetached("/opt/apps/com.github.ccc-proc-info-plugin/files/tools/proc-info-window",
                                QStringList{QString("--%1").arg(EXEC_PARAMS_CFG_KEY_OPEN_ABOUT_DLG)});;
    });
    //// 连接当前会话属性改变信号
    // 注册login1会话结构体
    qRegisterMetaType<LoginSessionInfo>("LoginSessionInfo");
    qDBusRegisterMetaType<LoginSessionInfo>();
    qRegisterMetaType<QList<LoginSessionInfo>>("QList<LoginSessionInfo>");
    qDBusRegisterMetaType<QList<LoginSessionInfo>>();
    // 获取当前用户uid
    unsigned int currentUid = getuid();
    QDBusInterface ifc(LOGIN1_DBUS_NAME, LOGIN1_DBUS_PATH, LOGIN1_MANAGER_DBUS_IFC,
                       QDBusConnection::systemBus(), this);
    QDBusReply<QList<LoginSessionInfo>> reply = ifc.call("ListSessions");
    if (!reply.isValid()) {
        qWarning() << Q_FUNC_INFO << "ListSessions reply error" << reply.error();
    }
    QList<LoginSessionInfo> sessionList = reply.value();

    for (const LoginSessionInfo &info : sessionList) {
        if (currentUid == info.uid) {
            QDBusInterface *selfSessionIfc = new QDBusInterface(LOGIN1_DBUS_NAME,
                                                                info.objPath.path(),
                                                                PROPS_DBUS_IFC,
                                                                QDBusConnection::systemBus(), this);

            connect(selfSessionIfc, SIGNAL(PropertiesChanged(const QString &, const QVariantMap &, const QStringList &)), this,
                    SLOT(onLogin1SelfSessionPropsChanged(const QString &, const QVariantMap &, const QStringList &)));
        }
    }

    // post initialize
    QTimer *updateTimer = new QTimer;
    updateTimer->setInterval(UpdateIntervalS * 1000);
    connect(updateTimer, &QTimer::timeout, this, &ProcInfoWidget::updateProcInfo);
    updateTimer->start();

    loadCfgs();
    enableNetDataMonitor(m_enableNetDataMonitor);
}

QSize ProcInfoWidget::sizeHint() const
{
    int width = this->fontMetrics().width("99.9M/M");
    return QSize(width, height());
}

bool ProcInfoWidget::loadCfgsFromFilePath(const QString &path)
{
    QFile file(path);
    if (!file.open(QIODevice::OpenModeFlag::ReadOnly)) {
        qWarning() << Q_FUNC_INFO << path << "open failed";
        file.close();
        return false;
    }

    QByteArray content = file.readAll();
    file.close();
    QJsonParseError err;
    QJsonDocument doc = QJsonDocument::fromJson(content, &err);
    if (err.error != QJsonParseError::NoError) {
        qCritical() << Q_FUNC_INFO << "parse json failed";
        return false;
    }

    QJsonObject mainJsonObj = doc.object();
    QJsonObject netJsonObj = mainJsonObj.take(CFG_KEY_NET).toObject();

    m_enableNetDataMonitor = netJsonObj.value(CFG_KEY_NET_DATA_MONITOR_SWITCH).toBool();

    return true;
}

void ProcInfoWidget::loadCfgs()
{
    if (!loadCfgsFromFilePath(CfgFilePath)) {
        loadCfgsFromFilePath(DefaultCfgFilePath);
    }
}

void ProcInfoWidget::enableNetDataMonitor(bool enable)
{
    m_enableNetDataMonitor = enable;
    m_netMonitorInter->call(QDBus::Block, "EnableNetFlowMonitor", enable);
}

void ProcInfoWidget::getNetBytes(unsigned long &upload, unsigned long &download)
{
    static unsigned long oldrbytes, oldsbytes;
    //获得net信息
    char buffer[1024];
    FILE *fp = fopen("/proc/net/dev", "r");
    if (fp == NULL) {
        perror("Could not open netdev file");
        return;
    }
    char *ret = fgets(buffer, sizeof(buffer) - 1, fp);
    if (ret == NULL) {
        perror("Could not read netdev file");
        fclose(fp);
        return;
    }
    unsigned long rbytes, sbytes, tmpr, tmps;
    char devname[1024];
    tmpr = tmps = rbytes = sbytes = 0;
    while(true)
    {
        ret = fgets(buffer, sizeof(buffer) - 1, fp);
        if (ret == NULL)
            break;
        sscanf(buffer, "%s %lu %*lu %*lu %*lu %*lu %*lu %*lu %*lu %lu",
               devname, &tmpr, &tmps);
        if(strcmp(devname, "lo:") == 0)
            continue;
        rbytes += tmpr;
        sbytes += tmps;
    }
    fclose(fp);
    tmpr = ( oldrbytes == 0 ? 0 : rbytes - oldrbytes );
    tmps=( oldsbytes == 0 ? 0 : sbytes - oldsbytes );
    oldrbytes = rbytes;
    oldsbytes = sbytes;

    upload = tmps; // 单位：Byte
    download = tmpr;
}

void ProcInfoWidget::updateProcInfo()
{
    if (!m_netMonitorInter->isValid()) {
        m_speedLabel->setText("0K/s");
        return;
    }

    QDBusReply<ProcInfoList> reply = m_netMonitorInter->call(QDBus::CallMode::Block, "GetProcInfoList");
    if (!reply.isValid()) {
        qDebug() << Q_FUNC_INFO << reply.error();
        return;
    }
    ProcInfoList procInfoList = reply.value();

    if (procInfoList.isEmpty()) {
        qInfo() << Q_FUNC_INFO << "procInfoList.isEmpty()";
        return;
    }

    double dDownloadSpeed = 0.0;
    double dUploadSpeed = 0.0;

    for (ProcInfo info : procInfoList) {
        dDownloadSpeed += info.dDownloadSpeed;
        dUploadSpeed += info.dUploadSpeed;
    }

    double dTotalSpeed = dDownloadSpeed + dUploadSpeed;
    QString sTotalSpeed = formatBytesByDefault(dTotalSpeed).append("/s");

    m_speedLabel->setText(sTotalSpeed);
    Q_EMIT sendSpeed(dUploadSpeed, dDownloadSpeed);
}

void ProcInfoWidget::updateNetSpeed()
{
    unsigned long downloadBytes;
    unsigned long uploadBytes;
    getNetBytes(uploadBytes, downloadBytes);

    unsigned long downloadSpeed = downloadBytes / UpdateIntervalS;
    unsigned long uploadSpeed = uploadBytes / UpdateIntervalS;
    unsigned long totalSpeed = downloadSpeed + uploadSpeed;
    QString sTotalSpeed = formatBytesByDefault(totalSpeed).append("/s");

    m_speedLabel->setText(sTotalSpeed);
    Q_EMIT sendSpeed(uploadSpeed, downloadSpeed);
}

void ProcInfoWidget::onLogin1SelfSessionPropsChanged(const QString &ifcName, const QVariantMap &propMap, const QStringList &args)
{
    Q_UNUSED(args);
    qDebug() << Q_FUNC_INFO;
    if (LOGIN1_SESSION_DBUS_IFC != ifcName) {
        qWarning() << Q_FUNC_INFO << "changed properties is not session interface's" << ifcName;
        return;
    }

    if (propMap.keys().contains("Active")) {
        bool isActived = propMap.value("Active").toBool();
        // 如果当前会话激活状态改变
        if (isActived) {
            qInfo() << Q_FUNC_INFO << "current session actived";
            loadCfgs();
            enableNetDataMonitor(m_enableNetDataMonitor);
        }
    }
}
